/*
 * Created on 2007-10-20
 *
 * TODO
 */
package jacky.lanlan.song.extension.spring.struts;

import jacky.lanlan.song.extension.struts.URLMethodMapper;
import jacky.lanlan.song.extension.struts.annotation.Action;
import jacky.lanlan.song.extension.struts.annotation.Execution;

import java.lang.reflect.Method;
import java.util.*;

import org.apache.log4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanFactory;

/**
 * Spring BeanFactoryPostProcessor，给所有实现了AnnotationConfigAction的Struts Action增加一个
 * URL别名，该URL为Action的执行方法的映射。
 * <p>
 * 注意，这个Bean只适合当使用Spring来管理Struts的Action时有用。
 * <p>
 * 你可以提供一个URL到Action执行方法的映射器 <code>URLMethodMapper</code>，这个Bean将使用提供的 映射器来配置别名。
 * 
 * @author Jacky.Song
 * @see #URLMethodMapper
 */
public class ConfigStrutsActionBean implements BeanFactoryPostProcessor {

	private final static Logger log = Logger.getLogger(ConfigStrutsActionBean.class);

	private URLMethodMapper urlMethodMapper;
	
	private String basePackage;
	
	/**
	 * 设置Action所在的包
	 */
	public void setBasePackage(String basePackage) {
		this.basePackage = basePackage;
	}
	
	/**
	 * 设置自定义的URL到Action执行方法的映射器。
	 * 
	 * @param urlMethodMapper
	 */
	public void setUrlMethodMapper(URLMethodMapper urlMethodMapper) {
		this.urlMethodMapper = urlMethodMapper;
	}
	
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		// 找到所有用标注配置的StrutsActionBean(以下简称Bean)
		// key=Action Bean Name，value=对应的Action Bean Class
		Map<String,Class<?>> actionBeansMap=new HashMap<String,Class<?>> ();
		for (String name : beanFactory.getBeanNamesForType(null)) {// 遍历每个Spring Bean
			Class<?> type=beanFactory.getBean(name).getClass();
			if (org.apache.struts.action.Action.class.isAssignableFrom(type) || // 如果继承自Struts的Action
					type.isAnnotationPresent(Action.class)) {// 或者有@Action标注
				actionBeansMap.put(name, type);
			}
		}
		
		if(log.isDebugEnabled()) {
			log.debug("找到由Spring托管的Action Bean:" + actionBeansMap.keySet());
		}
		
		String basePackage=findBasePackage(actionBeansMap.values());
		
		if(log.isDebugEnabled()) {
			log.debug("找到Action Bean所在包:" + basePackage);
			log.debug("开始根据标注重新配置Action Bean...");
			if(this.urlMethodMapper==null) {
				log.debug("使用默认的URLMethodMapper");
			}
			else {
				log.debug("使用指定的URLMethodMapper : " + this.urlMethodMapper.getClass());
			}
		}

		// 根据标注重新配置Bean(主要是映射自定义的URL为Bean别名，建立URL和Bean之间的映射，每个URL对应一个别名)
		for (String beanName : actionBeansMap.keySet()) {
			Class<?> beanClass=beanFactory.getBean(beanName).getClass();

			if (beanFactory instanceof AbstractBeanFactory) {

				final Collection<Method> methods = getPublicMethods(beanClass, beanClass);// Bean的所有方法

				// register beans for each url (分别注册这些方法到对应的URL)
				for (Method method : methods) {
					// 过滤出Bean的执行业务逻辑的方法(Execution方法)
					if (method.isAnnotationPresent(Execution.class)) {
						// 方法到url映射
						String url=mapMethodToURL(basePackage,method);
						
						if (!isAliase(beanFactory.getAliases(beanName), url)) {// 如果Bean还没有这个别名
							beanFactory.registerAlias(beanName, url);// 映射URL为Bean别名
							if(log.isDebugEnabled()) {
								log.debug("建立映射 Action Bean[" + beanName + "] -> URL[" + url + "]");
							}
						}
					}
				}
			} else {
				log.error("Unable to self-register; beanFactory bean was of an unsupported type.");
				throw new BeanNotOfRequiredTypeException(beanName,
						AbstractBeanFactory.class, beanFactory.getClass());
			}
		}
		if(log.isDebugEnabled()) {
			log.debug("Action Bean 配置完成!");
			log.debug("");
		}
	}
	// url是否是一个别名
  private boolean isAliase(String[] aliases, String url) {
		if (url == null) {
			for (int i = 0; i < aliases.length; i++) {
				if (aliases[i] == null) { return true; }
			}
		}
		else {
			for (int i = 0; i < aliases.length; i++) {
				if (url.equals(aliases[i])) { return true; }
			}
		}
		return false;
	 }
	
	private String findBasePackage(Collection<Class<?>> classes) {
		if(basePackage==null) {
			log.error("没有设置basePackage!!!");
			throw new UnsatisfiedDependencyException("",getClass().getSimpleName(),
					"basePackage",
					"使用默认的URLMethodMapper，但没有设置basePackage!!!");
		}
		return this.basePackage;
		/*
		//得到所有Action Bean的全限定类名
		String [] fullClassNameArray=new String[classes.size()];
		Class<?> [] actions=classes.toArray(new Class<?>[0]);
		for (int i=0;i<fullClassNameArray.length;i++) {
			fullClassNameArray[i]=actions[i].getName();
		}
		String minPkg=fullClassNameArray[0];//最短包的包名
		for (int i = 0; i < fullClassNameArray.length; i++) {
			if(minPkg.split("\\.").length>fullClassNameArray[i].split("\\.").length) {
				minPkg=fullClassNameArray[i];
			}
		}
		minPkg=minPkg.substring(0, minPkg.lastIndexOf('.'));//去掉类名
		//检测最短包是否包含所有类
		boolean containAllActions = true;
		for (String className : fullClassNameArray) {
			if(!className.contains(minPkg)) {
				containAllActions=false;
				break;
			}
		}
		return containAllActions ? minPkg : //如果包含，直接返回
			minPkg.substring(0, minPkg.lastIndexOf('.'));//如果没有包含，则取最短包上一级作为basePackage
		 */
	}

	// 映射执行方法到URL
	private String mapMethodToURL(String basePackage,Method executionMethod) {
		if (this.urlMethodMapper == null) {// 如果没有自定义映射器，使用默认的
			return URLMethodMapper.DEFAULT.mapMethodToURL(executionMethod,basePackage);
		}
		return urlMethodMapper.mapMethodToURL(executionMethod,basePackage);
	}
	
  private Collection<Method> getPublicMethods(Class<?> clazz, final Class<?> upto) {
    // collect methods to register (include methods for all classes up to and including this one)
    final Collection<Method> methods = new ArrayList<Method>();
    while ( !clazz.equals( upto.getSuperclass() ) ) {
        methods.addAll( Arrays.asList( clazz.getMethods() ) );
        clazz = clazz.getSuperclass();
    }
    return methods;
	}

}
